CodeMirror.defineMode('rst', function (config, options) {
  function setState(state, fn, ctx) {
    state.fn = fn;
    setCtx(state, ctx);
  }

  function setCtx(state, ctx) {
    state.ctx = ctx || {};
  }

  function setNormal(state, ch) {
    if (ch && (typeof ch !== 'string')) {
      var str = ch.current();
      ch = str[str.length - 1];
    }

    setState(state, normal, {back:ch});
  }

  function hasMode(mode) {
    if (mode) {
      var modes = CodeMirror.listModes();

      for (var i in modes) {
        if (modes[i] == mode) {
          return true;
        }
      }
    }

    return false;
  }

  function getMode(mode) {
    if (hasMode(mode)) {
      return CodeMirror.getMode(config, mode);
    } else {
      return null;
    }
  }

  var verbatimMode = getMode(options.verbatim);
  var pythonMode = getMode('python');

  var reSection = /^[!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]/;
  var reDirective = /^\s*\w([-:.\w]*\w)?::(\s|$)/;
  var reHyperlink = /^\s*_[\w-]+:(\s|$)/;
  var reFootnote = /^\s*\[(\d+|#)\](\s|$)/;
  var reCitation = /^\s*\[[A-Za-z][\w-]*\](\s|$)/;
  var reFootnoteRef = /^\[(\d+|#)\]_/;
  var reCitationRef = /^\[[A-Za-z][\w-]*\]_/;
  var reDirectiveMarker = /^\.\.(\s|$)/;
  var reVerbatimMarker = /^::\s*$/;
  var rePreInline = /^[-\s"([{</:]/;
  var rePostInline = /^[-\s`'")\]}>/
  :.,
  ;
  ! ?\\_
  ]/
  ;
  var reEnumeratedList = /^\s*((\d+|[A-Za-z#])[.)]|\((\d+|[A-Z-a-z#])\))\s/;
  var reBulletedList = /^\s*[-\+\*]\s/;
  var reExamples = /^\s+(>>>|In \[\d+\]:)\s/;

  function normal(stream, state) {
    var ch, sol, i;

    if (stream.eat(/\\/)) {
      ch = stream.next();
      setNormal(state, ch);
      return null;
    }

    sol = stream.sol();

    if (sol && (ch = stream.eat(reSection))) {
      for (i = 0; stream.eat(ch); i++);

      if (i >= 3 && stream.match(/^\s*$/)) {
        setNormal(state, null);
        return 'section';
      } else {
        stream.backUp(i + 1);
      }
    }

    if (sol && stream.match(reDirectiveMarker)) {
      if (!stream.eol()) {
        setState(state, directive);
      }

      return 'directive-marker';
    }

    if (stream.match(reVerbatimMarker)) {
      if (!verbatimMode) {
        setState(state, verbatim);
      } else {
        var mode = verbatimMode;

        setState(state, verbatim, {
          mode:mode,
          local:mode.startState()
        });
      }

      return 'verbatim-marker';
    }

    if (sol && stream.match(reExamples, false)) {
      if (!pythonMode) {
        setState(state, verbatim);
        return 'verbatim-marker';
      } else {
        var mode = pythonMode;

        setState(state, verbatim, {
          mode:mode,
          local:mode.startState()
        });

        return null;
      }
    }

    if (sol && (stream.match(reEnumeratedList) ||
      stream.match(reBulletedList))) {
      setNormal(state, stream);
      return 'list';
    }

    function testBackward(re) {
      return sol || !state.ctx.back || re.test(state.ctx.back);
    }

    function testForward(re) {
      return stream.eol() || stream.match(re, false);
    }

    function testInline(re) {
      return stream.match(re) && testBackward(/\W/) && testForward(/\W/);
    }

    if (testInline(reFootnoteRef)) {
      setNormal(state, stream);
      return 'footnote';
    }

    if (testInline(reCitationRef)) {
      setNormal(state, stream);
      return 'citation';
    }

    ch = stream.next();

    if (testBackward(rePreInline)) {
      if ((ch === ':' || ch === '|') && stream.eat(/\S/)) {
        var token;

        if (ch === ':') {
          token = 'role';
        } else {
          token = 'replacement';
        }

        setState(state, inline, {
          ch:ch,
          wide:false,
          prev:null,
          token:token
        });

        return token;
      }

      if (ch === '*' || ch === '`') {
        var orig = ch,
          wide = false;

        ch = stream.next();

        if (ch == orig) {
          wide = true;
          ch = stream.next();
        }

        if (ch && !/\s/.test(ch)) {
          var token;

          if (orig === '*') {
            token = wide ? 'strong' : 'emphasis';
          } else {
            token = wide ? 'inline' : 'interpreted';
          }

          setState(state, inline, {
            ch:orig, // inline() has to know what to search for
            wide:wide, // are we looking for `ch` or `chch`
            prev:null, // terminator must not be preceeded with whitespace
            token:token            // I don't want to recompute this all the time
          });

          return token;
        }
      }
    }

    setNormal(state, ch);
    return null;
  }

  function inline(stream, state) {
    var ch = stream.next(),
      token = state.ctx.token;

    function finish(ch) {
      state.ctx.prev = ch;
      return token;
    }

    if (ch != state.ctx.ch) {
      return finish(ch);
    }

    if (/\s/.test(state.ctx.prev)) {
      return finish(ch);
    }

    if (state.ctx.wide) {
      ch = stream.next();

      if (ch != state.ctx.ch) {
        return finish(ch);
      }
    }

    if (!stream.eol() && !rePostInline.test(stream.peek())) {
      if (state.ctx.wide) {
        stream.backUp(1);
      }

      return finish(ch);
    }

    setState(state, normal);
    setNormal(state, ch);

    return token;
  }

  function directive(stream, state) {
    var token = null;

    if (stream.match(reDirective)) {
      token = 'directive';
    } else if (stream.match(reHyperlink)) {
      token = 'hyperlink';
    } else if (stream.match(reFootnote)) {
      token = 'footnote';
    } else if (stream.match(reCitation)) {
      token = 'citation';
    } else {
      stream.eatSpace();

      if (stream.eol()) {
        setNormal(state, stream);
        return null;
      } else {
        stream.skipToEnd();
        setState(state, comment);
        return 'comment';
      }
    }

    setState(state, body, {start:true});
    return token;
  }

  function body(stream, state) {
    var token = 'body';

    if (!state.ctx.start || stream.sol()) {
      return block(stream, state, token);
    }

    stream.skipToEnd();
    setCtx(state);

    return token;
  }

  function comment(stream, state) {
    return block(stream, state, 'comment');
  }

  function verbatim(stream, state) {
    if (!verbatimMode) {
      return block(stream, state, 'verbatim');
    } else {
      if (stream.sol()) {
        if (!stream.eatSpace()) {
          setNormal(state, stream);
        }

        return null;
      }

      return verbatimMode.token(stream, state.ctx.local);
    }
  }

  function block(stream, state, token) {
    if (stream.eol() || stream.eatSpace()) {
      stream.skipToEnd();
      return token;
    } else {
      setNormal(state, stream);
      return null;
    }
  }

  return {
    startState:function () {
      return {fn:normal, ctx:{}};
    },

    copyState:function (state) {
      return {fn:state.fn, ctx:state.ctx};
    },

    token:function (stream, state) {
      var token = state.fn(stream, state);
      return token;
    }
  };
});

CodeMirror.defineMIME("text/x-rst", "rst");
